﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Web;
using System.Web.Caching;
using System.Web.Hosting;

namespace AxeFrog.Web
{
	/// <summary>
	/// Provides the ability for the application to search assemblies for views and partial views.
	/// </summary>
	public class AssemblyResourceVirtualPathProvider : VirtualPathProvider
	{
		private readonly Dictionary<string, Assembly> _nameAssemblyCache;

		public AssemblyResourceVirtualPathProvider()
		{
			_nameAssemblyCache = new Dictionary<string, Assembly>(StringComparer.InvariantCultureIgnoreCase);
		}

		private bool IsAppResourcePath(string virtualPath)
		{
			string checkPath = VirtualPathUtility.ToAppRelative(virtualPath);

			return checkPath.StartsWith("~/App_Resource/",
			                            StringComparison.InvariantCultureIgnoreCase);
		}

		public override bool FileExists(string virtualPath)
		{
			return (IsAppResourcePath(virtualPath) ||
			        base.FileExists(virtualPath));
		}

		public override VirtualFile GetFile(string virtualPath)
		{
			if (IsAppResourcePath(virtualPath))
			{
				return new AssemblyResourceFile(_nameAssemblyCache, virtualPath);
			}

			return base.GetFile(virtualPath);
		}

		public override CacheDependency GetCacheDependency(
			string virtualPath,
			IEnumerable virtualPathDependencies,
			DateTime utcStart)
		{
			if (IsAppResourcePath(virtualPath))
			{
				return null;
			}

			return base.GetCacheDependency(virtualPath,
			                               virtualPathDependencies, utcStart);
		}

		private class AssemblyResourceFile : VirtualFile
		{
			private readonly IDictionary<string, Assembly> _nameAssemblyCache;
			private readonly string _assemblyPath;

			public AssemblyResourceFile(IDictionary<string, Assembly> nameAssemblyCache, string virtualPath) : 
				base(virtualPath)
			{
				_nameAssemblyCache = nameAssemblyCache;
				_assemblyPath = VirtualPathUtility.ToAppRelative(virtualPath);
			}

			public override Stream Open()
			{
				// ~/App_Resource/WikiExtension.dll/WikiExtension/Presentation/Views/Wiki/Index.aspx
				string[] parts = _assemblyPath.Split(new [] {'/'}, 4);

				// TODO: should assert and sanitize 'parts' first
				string assemblyName = parts[2];
				string resourceName = parts[3].Replace('/', '.');

				Assembly assembly;

				lock(_nameAssemblyCache)
				{
					if (!_nameAssemblyCache.TryGetValue(assemblyName, out assembly))
					{
						var assemblyPath = Path.Combine(HttpRuntime.BinDirectory, assemblyName);
						assembly = Assembly.LoadFrom(assemblyPath);

						// TODO: Assert is not null
						_nameAssemblyCache[assemblyName] = assembly;
					}
				}

				Stream resourceStream = null;

				if (assembly != null)
				{
					resourceStream = assembly.GetManifestResourceStream(resourceName);
				}

				return resourceStream;
			}
		}
	}
}